﻿
#if NET5_0_OR_GREATER


using System;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.IO;

namespace Dot5Demo.Models
{
    /*微软JSON序列化
    https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-handle-overflow

序列化为 UTF-8
    序列化为 UTF-8 字节数组比使用基于字符串的方法大约快 5-10%。 
    出现这种差别的原因是字节（作为 UTF-8）不需要转换为字符串 (UTF-16)
重用 JsonSerializerOptions 实例
    如果你通过相同的选项重复使用 JsonSerializerOptions，则请勿在每次使用时都创建新的 JsonSerializerOptions 实例
    当向序列化程序传递新的选项实例时，在对象图中每种类型的第一次序列化期间，序列化程序将执行一个预热阶段。
    此预热包括创建序列化所需的元数据的缓存。元数据包括对属性 Getter 的委托、Setter、构造函数参数、指定特性等。 
    此元数据缓存存储在选项实例中。 相同的预热过程和缓存适用于反序列化。
    JsonSerializerOptions 实例中元数据缓存的大小取决于要序列化的类型的数量。 
    如果向序列化程序传递多种类型（例如动态生成的类型），缓存大小将继续增长，最终可导致 OutOfMemoryException。
    JsonSerializerOptions optionsCopy = new JsonSerializerOptions(options);//将使用现有实例相同的选项创建实例
自定义属性名和值
    [JsonPropertyName("Wind")]
        同时适用于两个方向（序列化和反序列化）
        优先于属性命名策略
    自定义JSON属性命名策略
        public class MyJsonNamingPolicy : JsonNamingPolicy {//...}
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = new UpperCaseNamingPolicy(),
            WriteIndented = true
        };
        适用于序列化和反序列化
        可由 [JsonPropertyName] 特性替代
忽略属性
    忽略单个属性
        在要忽略的属性上添加特性标记：[JsonIgnore],可以指定 Condition 属性来指定条件排除
    忽略所有只读属性
        如果属性包含公共 getter 而不是公共 setter，则属性为只读。
        若要忽略只读属性，需将 JsonSerializerOptions.IgnoreReadOnlyProperties 设置为 true
        仅适用于序列化。 在反序列化过程中，默认情况下会忽略只读属性
        仅适用于属性。 若要在序列化字段时忽略只读字段，请使用 JsonSerializerOptions.IgnoreReadOnlyFields 全局设置
    忽略所有 null 值属性
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    忽略所有默认值属性
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
        还会阻止对 null 值引用类型和可为 null 的值类型属性进行序列化
允许不正确的JSON
    允许注释
        ReadCommentHandling = JsonCommentHandling.Skip
    允许尾随逗号
        AllowTrailingCommas = true
    允许在整个输入对象图中序列化带引号的数字或接受带引号的数字
        NumberHandling =
                    JsonNumberHandling.AllowReadingFromString |
                    JsonNumberHandling.WriteAsString,
        若要允许或写入特定属性、字段或类型的带引号的数字，请使用 [JsonNumberHandling] 特性
处理溢出JSON
    反序列化时，可能会在 JSON 中收到不是由目标类型的属性表示的数据
    若要捕获额外数据（如这些属性），请将 [JsonExtensionData] 特性应用于类型
        Dictionary<string,object> 的属性
        Dictionary<string,JsonElement> 的属性
    ExtensionData 属性名称不会出现在 JSON 中。 
    此行为使 JSON 可以进行往返，而不会丢失任何不会以其他方式进行反序列化的额外数据
保留引用并处理循环引用
    若要保留引用并处理循环引用，请将 ReferenceHandler 设置为 Preserve。 此设置会导致以下行为：
        在序列化时：
            编写复杂类型时，序列化程序还会写入元数据属性（$id、$values 和 $ref）。
        在反序列化时：
            需要元数据（虽然不是必需的），并且反序列化程序会尝试理解它。
    注意：功能不能用于保留值类型或不可变类型，在反序列化时，将在读取整个有效负载后创建不可变类型的实例。 
    因此，如果对同一实例的引用出现在 JSON 有效负载中，则无法对其进行反序列化
    https://docs.microsoft.com/zh-cn/dotnet/api/system.text.json.serialization.referencehandler.preserve
使用不可变类型和非公共访问器
    System.Text.Json 可以使用参数化构造函数，这可以反序列化不可变的类或结构
    对于类，如果唯一构造函数是参数化的构造函数，则将使用该构造函数
    对于结构或包含多个构造函数的类，通过应用 [JsonConstructor] 特性来指定要使用的构造函数。
    如果未使用该特性，则始终使用公共无参数构造函数（如果存在）。 该特性只能与公共构造函数一起使用。
    非公共属性访问器
        若要允许使用非公共属性访问器，请使用 [JsonInclude] 特性
多态序列化
    序列化派生类的属性
        不支持多态类型层次结构的序列化。
        如果属性定义为接口或抽象类，则即使运行时类型具有其他属性，也只会序列化对接口或抽象类定义的属性
        若要序列化前面示例中派生类型的属性，请使用以下方法之一：
            调用 Serialize 的重载，以便在运行时指定类型:jsonStr = JsonSerializer.Serialize(obj, obj.GetType(), options)
            将要序列化的对象声明为 object:jsonStr = JsonSerializer.Serialize<object>(obj, options)
        如果将较低级别的对象定义为类型 object，则可以对它们进行多态序列化
        将属性定义为 object 的相同方法适用于接口
        System.Text.Json 可以执行有限范围的多态序列化，但不能执行多态反序列化，若要支持多态反序列化，请创建转换器
在System.Text.Json中使用JSON document, Utf8JsonReader和Utf8JsonWriter
    https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-use-dom-utf8jsonreader-utf8jsonwriter?pivots=dotnet-5-0
    使用JsonDocument对加载的JSON数据随机访问
    使用Utf8JsonWriter类型构建定制化的序列化器
    使用Utf8JsonReader类型构建定制化的解释器和反序列化器
System.Text.Json支持的集合类型
    支持的类型：单维数组、交错数组
    不支持的类型：多维数组
    https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-supported-collection-types?pivots=dotnet-5-0
使用 System.Text.Json 自定义字符编码
    默认情况下，序列化程序会转义所有非 ASCII 字符。
    若要序列化一种或多种语言的字符集而不进行转义，请在创建 System.Text.Encodings.Web.JavaScriptEncoder 的实例时指定 Unicode 范围
    options = new JsonSerializerOptions
    {
        Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic),
        WriteIndented = true
    };
    序列化特定字符
        一种替代方法是指定要允许的单个字符，而不进行转义
        var encoderSettings = new TextEncoderSettings();
        encoderSettings.AllowCharacters('\u0436', '\u0430');
        encoderSettings.AllowRange(UnicodeRanges.BasicLatin);
        options = new JsonSerializerOptions
        {
            Encoder = JavaScriptEncoder.Create(encoderSettings),
            WriteIndented = true
        };
        jsonString = JsonSerializer.Serialize(weatherForecast, options);
    序列化所有字符
        若要最大程度地减少转义，可以使用 JavaScriptEncoder.UnsafeRelaxedJsonEscaping
使用源生成
    仅在.net6下支持
    https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-source-generation?pivots=dotnet-6-0
编写自定义转换器
    https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0
    重写内置转换器的默认行为
    编写自定义转换器，以使用当前版本中未包含的功能自定义或扩展 System.Text.Json
     */

    /// <summary>
    /// 
    /// </summary>
    public static class JSonSerializeUtil
    {

        private static JsonSerializerOptions Options;
        private static JsonSerializerOptions WebOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);

        static JSonSerializeUtil()
        {
            Options = new JsonSerializerOptions()
            {
#if DEBUG
                WriteIndented = true,
#else
                WriteIndented = false,
#endif
//属性命名
                //默认情况下，反序列化会查找 JSON 与目标对象属性之间区分大小写的属性名称匹配
                PropertyNameCaseInsensitive = true,
                //指定自定义属性命名策略
                PropertyNamingPolicy = new MyJsonNamingPolicy(),
                //Camel 大小写字典键,如果要序列化的对象的属性为 Dictionary<string,TValue> 类型，则 string 键可转换为 camel 大小写
                DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
                //默认情况下，枚举会序列化为数字。 若要将枚举名称序列化为字符串,请使用 JsonStringEnumConverter
                Converters =
                {
                    new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
                },
//忽略属性
                //序列化时忽略所有的只读属性
                //若要在序列化字段时忽略只读字段，请使用 JsonSerializerOptions.IgnoreReadOnlyFields 全局设置
                IgnoreReadOnlyProperties = true,
                //忽略所有的null属性
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
                //忽略所有默认值属性
                //DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
                //还会阻止对 null 值引用类型和可为 null 的值类型属性进行序列化
//处理不规范JSON
                //是否允许注释（默认情况下，JSON 中不允许使用注释和尾随逗号）
                ReadCommentHandling = JsonCommentHandling.Skip,
                //是否允许结尾的逗号
                AllowTrailingCommas = true,
                //允许将数字序列化为字符串
                //若要允许或写入特定属性、字段或类型的带引号的数字，请使用 [JsonNumberHandling] 特性
                NumberHandling =
                    JsonNumberHandling.AllowReadingFromString |
                    JsonNumberHandling.WriteAsString,

                //保留引用并处理循环引用
                //为了确定对象是否相等，System.Text.Json 在比较两个对象实例时使用引用相等性 (Object.ReferenceEquals(Object, Object))
                // 而不是值相等性(Object.Equals(Object) 的 ReferenceEqualityComparer.Instance
                ReferenceHandler = ReferenceHandler.Preserve,
                //指定编码 自定义字符编码
                //默认情况下，序列化程序会转义所有非 ASCII 字符。 即，会将它们替换为 \uxxxx，其中 xxxx 为字符的 Unicode 代码
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),


            };
        }

        public static string Serialize<T>(T t)
        {
            try
            {
                return JsonSerializer.Serialize(t, t.GetType(), Options);
            }
            catch (Exception ex)
            {
#pragma warning disable CA2200 // 再次引发以保留堆栈详细信息
                throw ex;
#pragma warning restore CA2200 // 再次引发以保留堆栈详细信息
            }
        }

        public static T Deserialize<T>(string str)
        {
            try
            {
                return JsonSerializer.Deserialize<T>(str, Options);
            }
            catch (Exception ex)
            {
#pragma warning disable CA2200 // 再次引发以保留堆栈详细信息
                throw ex;
#pragma warning restore CA2200 // 再次引发以保留堆栈详细信息
            }
        }

        public static async Task<string> SerilizeAsync<T>(T t)
        {
            try
            {
                using Stream stream = new MemoryStream();
                await JsonSerializer.SerializeAsync(stream, t, t.GetType(), Options);
                stream.Position = 0;
                using var reader = new StreamReader(stream);
                return reader.ReadToEnd();
                //return await r.ContinueWith<string>(t =>
                //{
                //    stream.Position = 0;
                //    var buff = new byte[stream.Length];
                //    stream.Read(buff, 0, buff.Length);
                //    return Encoding.UTF8.GetString(buff);
                //});
            }
            catch (Exception ex)
            {
#pragma warning disable CA2200 // 再次引发以保留堆栈详细信息
                throw ex;
#pragma warning restore CA2200 // 再次引发以保留堆栈详细信息
            }
        }

        public static async Task<T> DeserializeAsync<T>(string jstr)
        {
            var obj = await DeserializeAsync(jstr, typeof(T));
            if (obj is T)
            {
                return (T)obj;
            }
            return default(T);
        }

        public static async Task<object> DeserializeAsync(string jstr, Type type)
        {
            if (string.IsNullOrEmpty(jstr))
                return null;
            try
            {
                using Stream stream = new MemoryStream();
                var buff = Encoding.UTF8.GetBytes(jstr);
                stream.Write(buff, 0, buff.Length);
                stream.Position = 0;
                return await JsonSerializer.DeserializeAsync(stream, type, Options);
            }
            catch (Exception ex)
            {
#pragma warning disable CA2200 // 再次引发以保留堆栈详细信息
                throw ex;
#pragma warning restore CA2200 // 再次引发以保留堆栈详细信息
            }
        }
    }

    public class MyJsonNamingPolicy : JsonNamingPolicy
    {
        public override string ConvertName(string name)
        {
            return name.Replace(' ', '@')+"&DIY";
        }
    }
}

#endif //NET5_0_OR_GREATER